package websem.database;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import websem.WebsemProperties;

public class SPARQLClient {

    /**
     * URI of the remote SPARQL server
     */
    private String endpointUri = null;

    public SPARQLClient() {
        WebsemProperties prop = new WebsemProperties();
    	this.endpointUri = prop.getSparqlURI();
    }

    public String getEndpointUri() {
        return endpointUri;
    }

    /**
     * run a SPARQL query (select) on the remote server. Get related children
     * @param queryString 
     */
    public Iterable<Map<String, String>> select(String queryString) {
        
    	// Get query string into document, then init resultNodes
    	Document document = executeQuery(queryString);
        List<Map<String, String>> finalResultsList = new LinkedList<Map<String, String>>();
        NodeList resultNodesList = document.getElementsByTagName("result");
        
        // For each result nodes
        for (int i = 0; i < resultNodesList.getLength(); i++) {
            Node currentResultNode = resultNodesList.item(i);
            
            // If current result node exists
            if (currentResultNode != null && currentResultNode.getNodeType() == Node.ELEMENT_NODE) {
                Map<String, String> currentResultMap = new HashMap<String, String>();
                finalResultsList.add(currentResultMap);
                
                // Create a new node list to save nodes bound to the search
                NodeList bindingNodes = currentResultNode.getChildNodes();
                
                // For each child nodes of current result node
                for (int j = 0; j < bindingNodes.getLength(); j++) {
                    Node currentBindingNode = bindingNodes.item(j);
                    
                    // If current binding node exists
                    if (currentBindingNode != null && 
                    	currentBindingNode.getNodeType() == Node.ELEMENT_NODE && 
                    	currentBindingNode.getNodeName().equals("binding")) {
                        
                    	String currentValueOfNode = "";
                    	String currentBindingNodeName = currentBindingNode.getAttributes().getNamedItem("name").getTextContent();
                        NodeList currentBindingNodeChildren = currentBindingNode.getChildNodes();
                        
                        // For each current binding node children's
                        for (int k = 0; k < currentBindingNodeChildren.getLength(); k++) {
                        	
                        	// Get childs item in order to get values
                            Node bindingChild = currentBindingNodeChildren.item(k);
                            if (bindingChild != null && bindingChild.getNodeType() == Node.ELEMENT_NODE) {
                                currentValueOfNode = bindingChild.getTextContent();
                                break;
                            }
                        }
                        
                        // Add related results to maps
                        currentResultMap.put(currentBindingNodeName, currentValueOfNode);
                    }
                }
            }
        }

        return finalResultsList;
    }

    /**
     * run a SPARQL query (ask) on the remote server
     * @param queryString 
     */
    public boolean ask(String queryString) {
        Document document = executeQuery(queryString);
        NodeList nl = document.getElementsByTagName("boolean");
        Node n = nl.item(0);
        if (n != null && n.getTextContent().equals("true")) {
            return true;
        }
        return false;
    }

    /**
     * Return XML content from a SPARQL remote server
     * @param queryString - sparql query
     * @return
     */
    private Document executeQuery(String queryString) {
        try {
            URIBuilder builder = new URIBuilder();
            builder.setScheme("http");
            builder.setHost(this.endpointUri);
            builder.setPath("/query");
            builder.setParameter("query", queryString);
            builder.setParameter("output", "xml");
            URI uri = builder.build();

            DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            if (parser.parse(uri.toASCIIString()) == null) {
            	System.out.println("ASCII NULL");
            }
            
            if (parser.parse(uri.toString()) == null) {
            	System.out.println("STRING NULL");
            }
            return parser.parse(uri.toString());
            
        } catch (SAXException ex) {
            Logger.getLogger(SPARQLClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(SPARQLClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ParserConfigurationException ex) {
            Logger.getLogger(SPARQLClient.class.getName()).log(Level.SEVERE, null, ex);
        } catch (URISyntaxException ex) {
            Logger.getLogger(SPARQLClient.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    /**
     * run a SPARQL update on the remote server
     * @param queryString 
     */
    public void update(String queryString) {
        try {
            DefaultHttpClient httpclient = new DefaultHttpClient();
            HttpPost httpPost = new HttpPost("http://" + endpointUri + "/update");
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            nvps.add(new BasicNameValuePair("update", queryString));
            httpPost.setEntity(new UrlEncodedFormEntity(nvps));
            HttpResponse response2 = httpclient.execute(httpPost);

            try {
                HttpEntity entity2 = response2.getEntity();
                // do something useful with the response body
                // and ensure it is fully consumed
                EntityUtils.consume(entity2);
            } finally {
                httpPost.releaseConnection();
            }
        } catch (IOException ex) {
            Logger.getLogger(SPARQLClient.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    /**
     * Checks if server is up
     * @return
     */
    public boolean isServerUp() {
    	String query = "ASK WHERE { ?s ?p ?o }";
        //System.out.println("QUERY SPARL SERVER :: " + this.ask(query));
    	
    	return this.ask(query);
        //return true;
    }
}
